package service

import (
	"JK-Junior-Go-Engineer-Camp/webook/internal/domain"
	"JK-Junior-Go-Engineer-Camp/webook/internal/repository"
	"context"
	"errors"
	"go.uber.org/zap"
	"golang.org/x/crypto/bcrypt"
)

var (
	ErrDuplicateEmail        = repository.ErrDuplicateUser
	ErrInvalidUserOrPassword = errors.New("用户不存在或者密码错误")
)

type UserService interface {
	Signup(ctx context.Context, u domain.User) error
	Login(ctx context.Context, email, password string) (domain.User, error)
	UpdateNonSensitiveInfo(ctx context.Context, user domain.User) error
	Profile(ctx context.Context, id int64) (domain.User, error)
	FindOrCreate(ctx context.Context, phone string) (domain.User, error)
	FindOrCreateByWechat(ctx context.Context, info domain.WechatInfo) (domain.User, error)
}

type userService struct {
	repo repository.UserRepository
}

func NewUserService(repo repository.UserRepository) UserService {
	return &userService{
		repo: repo,
	}
}

// Signup 方法名字与业务要保持一直，所以这里不是什么create或者insert
func (svc *userService) Signup(ctx context.Context, u domain.User) error {
	// 使用 bcrypt 加密算法
	hashPwd, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
	if err != nil {
		return err
	}
	u.Password = string(hashPwd)
	return svc.repo.Create(ctx, u)
}

// Login 方法名字与业务要保持一直
// 这里返回一个User，是因为后面使用Session会使用到，先提前在这里定义好
func (svc *userService) Login(ctx context.Context, email, password string) (domain.User, error) {
	user, err := svc.repo.FindByEmail(ctx, email)
	if err == repository.ErrUserNotFound {
		return domain.User{}, ErrInvalidUserOrPassword
	}
	if err != nil {
		return domain.User{}, err
	}
	// 检查密码是否正确
	err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
	if err != nil {
		return domain.User{}, ErrInvalidUserOrPassword
	}
	return user, nil
}

// UpdateNonSensitiveInfo 修改个人信息(无敏感信息)
func (svc *userService) UpdateNonSensitiveInfo(ctx context.Context, user domain.User) error {
	err := svc.repo.UpdateById(ctx, user)
	return err
}

// Profile 查询个人信息
func (svc *userService) Profile(ctx context.Context, id int64) (domain.User, error) {
	return svc.repo.FindById(ctx, id)
}

// FindOrCreate 通过手机号查看用户是否存在，如果不存在，创建一个新用户
func (svc *userService) FindOrCreate(ctx context.Context, phone string) (domain.User, error) {
	// 先找一找，我们认为 大部分用户时已存在的用户
	user, err := svc.repo.FindByPhone(ctx, phone)
	if err != repository.ErrUserNotFound {
		// 两种情况：
		// 1. err==nil, user可用
		// 2. err!=nil, 系统错误
		return user, err
	}
	// 如果没有找到
	err = svc.repo.Create(ctx, domain.User{
		Phone: phone,
	})
	// 两种可能：
	// 1. err 恰好是 唯一索引冲突错误(phone)
	// 2. 系统错误
	if err != nil && err != repository.ErrDuplicateUser {
		// 系统错误
		return domain.User{}, err
	}

	// 要么 err==nil, 要么 ErrDuplicateUser，都代表 用户已经存在了
	/*
		TODO:这里有个坑：你刚插入数据库，不一定能找得到
		 存在主从延迟，插入插的是主库，查询查的是从库，主从同步是有延迟的
		 如何解决的？
		 理论上来说这里应该强制走主库，但是我们现在还没有涉及都主从库，这里暂时标记一下就
	*/
	return svc.repo.FindByPhone(ctx, phone)
}

// FindOrCreateByWechat 微信扫码登录-查询用户是否存在，否则创建一个新用户
func (svc *userService) FindOrCreateByWechat(ctx context.Context,
	info domain.WechatInfo) (domain.User, error) {
	// 先找一找，我们认为 大部分用户是已存在的用户
	user, err := svc.repo.FindByWechat(ctx, info.Openid)
	if err != repository.ErrUserNotFound {
		return user, err
	}
	// 如果没有找到
	zap.L().Info("用户不存在，创建新用户", zap.Any("wechat_info", info))
	err = svc.repo.Create(ctx, domain.User{
		WechatInfo: info,
	})
	if err != nil && err != repository.ErrDuplicateUser {
		return domain.User{}, err
	}
	return svc.repo.FindByWechat(ctx, info.Openid)
}
